//自主实现shell


//#include <stdio.h>
//#include <string.h>
//#include <unistd.h>//sleep函数
//
////wait函数头文件
//#include <sys/types.h>
//#include <sys/wait.h>
//
////exit的函数
//#include <stdlib.h>
//
//#define NUM 2024
//#define SIZE 100
//#define SEP " "
////注意是""，而不是''.因为strtok的参数是char*类型
//
//int main()
//{
//  //0.始终不停止
//  while (1)
//  {
//    //1.打印类似于[xkjtx@locathost shell@]的信息
//    printf("xkjtx@locatehost myshell# ");//不要换行，否则就不像了，用fflush
//    fflush(stdout);
//
//    //2.获取命令行
//    char cmd_line[NUM];//可以定义为全局变量，注意每次使用前memset(cmd_line, '\0', sizeof cmd_line);
//    if (fgets(cmd_line, SIZE, stdin) == NULL)//推荐使用fgets，建立对标准输入输出流概念，方便以后学习基础IO
//    {
//      continue;//读取失败，重新读取
//    }
//    cmd_line[strlen(cmd_line) - 1] = '\0';
//
//    //char* fgets(char* str, int size, FILE* stream);//如果输入成功，则返回str的起始地址，否则为NULL
//    //3.拆分命令行
//    char* g_argv[NUM];
//    g_argv[0] = strtok(cmd_line, SEP);//第一次调用第一个参数传参目标字符串，后面都传NULL
//    int index = 1;
//    while(g_argv[index++] = strtok(NULL, SEP));
//
//  //  //检测分割是否正确
//  //  for (index = 0; g_argv[index]; index++)//C99标准不支持在for循环内定义int
//  //    printf("g_argv[%d] = %s\n", index, g_argv[index]);
//  
//    //4.TODO
//    //5.创建子进程//好处：不会影响父进程//shell本来就是媒婆招实习生，让实习生干活，不影响媒婆
//    pid_t id = fork();
//   
//
//    int status = 0;
//
//    if (id == 0)//child
//    {
//      execvp(g_argv[0], g_argv);//选这个函数的原因：(1)v：用的是数组，恰好有数组  (2)每个都要绝对路径，这是没有必要的，因此可以使用带p的函数
//      exit(1);//进行到这里，一定是函数调用失败
//    }
//
//    //这里一定是父进程
//    //father
//    //获取子进程信息
//    pid_t ret = waitpid(-1, &status, 0);
//    /*
//       #include <sys/types.h>
//       #include <sys/wait.h>
//
//       pid_t wait(int *status);
//
//       pid_t waitpid(pid_t pid, int *status, int options);
//
//     */
//    if (ret > 0)
//    {
//      printf("exit code = %d\n", WEXITSTATUS(status));
//    }
//  }
//  return 0;
//}




//
///*以上代码还不完善，不可以执行cd命令来切换路径，
// *原因：execvp函数是放在子进程中，但是切换路径应该影响的是父进程
//
// 解决方法：当执行的是cd命令时，不去执行子进程的代码，而是另行处理，这里要用到chdir函数，参数为：要切换到的路径
//chdir:change director的缩写
// * */
//
//
//
//
//#include <stdio.h>
//#include <string.h>
//#include <unistd.h>//sleep函数
//
////wait函数头文件
//#include <sys/types.h>
//#include <sys/wait.h>
//
////exit的函数
//#include <stdlib.h>
//
//#define NUM 2024
//#define SIZE 100
//#define SEP " "
////注意是""，而不是''.因为strtok的参数是char*类型
//
//extern char **environ;//全局的环境变量,方便后续子进程继承父进程的环境变量
//
//
//int main()
//{
//  //0.始终不停止
//  while (1)
//  {
//    //1.打印类似于[xkjtx@locathost shell@]的信息
//    printf("[xkjtx@locatehost myshell]# ");//不要换行，否则就不像了，用fflush
//    fflush(stdout);
//
//    //2.获取命令行
//    char cmd_line[NUM];//可以定义为全局变量，注意每次使用前memset(cmd_line, '\0', sizeof cmd_line);
//    if (fgets(cmd_line, SIZE, stdin) == NULL)//推荐使用fgets，建立对标准输入输出流概念，方便以后学习基础IO
//    {
//      continue;//读取失败，重新读取
//    }
//
//    cmd_line[strlen(cmd_line) - 1] = '\0';
//
//    //char* fgets(char* str, int size, FILE* stream);//如果输入成功，则返回str的起始地址，否则为NULL
//    //3.拆分命令行
//    char* g_argv[NUM];
//    g_argv[0] = strtok(cmd_line, SEP);//第一次调用第一个参数传参目标字符串，后面都传NULL
//    int index = 1;
//
//
//    //使得ls命令时带上颜色
//    if (strcmp(g_argv[0], "ls") == 0)
//    {
//      g_argv[index++] = "--color=auto";
//    }
//   
//    //使得ll命令也可以执行
//    if (strcmp(g_argv[0], "ll") == 0)
//    {
//      g_argv[0] = "ls";
//      g_argv[index++] = "-l";
//      g_argv[index++] = "--color=auto";
//    }
//    
//    while(g_argv[index++] = strtok(NULL, SEP));
//
//    if (strcmp(g_argv[0], "export") == 0)//export:设置环境变量，注意要放在while分割字符串的后面，因为要用到g_argv[1]
//    {
//      char envv[SIZE];
//      strcpy(envv, g_argv[1]);
//      putenv(envv);
//      //putenv(char* str);设置环境变量，要用函数来设置环境变量的原因：系统不允许我们直接修改环境变量，因此要借助函数
//      
//      continue;//后面的就不执行了
//    }
//
//  //  //检测分割是否正确
//  // for (index = 0; g_argv[index]; index++)//C99标准不支持在for循环内定义int
//  //   printf("g_argv[%d] = %s\n", index, g_argv[index]);
//  
//    //4.TODO
//    //内置命令的处理（要父进程去执行的命令，如cd命令）
//    //目前写法发现：cd ~, cd -, rm, export, vim等命令做不了
//    
//
//
//    //错误写法
//    // if (g_argv[0] == "cd")
//    if (strcmp(g_argv[0], "cd") == 0)
//    {
//      if (g_argv[1] != NULL)//路径合法
//      {
//        chdir(g_argv[1]);
//        continue;//不去创建子进程
//      }
//    }
//
//
//
//    //5.创建子进程//好处：不会影响父进程//shell本来就是媒婆招实习生，让实习生干活，不影响媒婆
//    pid_t id = fork();
//   
//
//    int status = 0;
//
//    if (id == 0)//child
//    {
//      execvp(g_argv[0], g_argv);//选这个函数的原因：(1)v：用的是数组，恰好有数组  (2)每个都要绝对路径，这是没有必要的，因此可以使用带p的函数
//      exit(1);//进行到这里，一定是函数调用失败
//    }
//
//    //这里一定是父进程
//    //father
//    //获取子进程信息
//    pid_t ret = waitpid(-1, &status, 0);
//    /*
//       #include <sys/types.h>
//       #include <sys/wait.h>
//
//       pid_t wait(int *status);
//
//       pid_t waitpid(pid_t pid, int *status, int options);
//
//     */
//    if (ret > 0)
//    {
//      printf("exit code = %d\n", WEXITSTATUS(status));
//    }
//  }
//  return 0;
//}





/*以下代码增加了  重定向功能， 也就是调用系统接口*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>//sleep函数

//wait函数头文件
#include <sys/types.h>
#include <sys/wait.h>

//open函数头文件
#include <sys/stat.h>
#include <fcntl.h>

//exit的函数
#include <stdlib.h>

#define NUM 2024
#define SIZE 100
#define SEP " "
//注意是""，而不是''.因为strtok的参数是char*类型

extern char **environ;//全局的环境变量,方便后续子进程继承父进程的环境变量

#define OUTPUT_REDIR 1
#define INPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0

int redir = NONE_REDIR;//没有重定向

char* CheckReDir(char* start)
{
  char* end = start + strlen(start) - 1;
  
  //printf("%d\n", strlen(start));
  
  while (end >= start)//从后向前找是否有重定向符号
  {
    if ((*end) == '<')
    {//ls < log.txt
      //输入重定向
      redir = INPUT_REDIR;
      (*(end-1)) = '\0';
      end++;//指向要被重定向的文件的首字母
      end++;
      break;
    }
    else if ((*end) == '>')
    {
      //输出重定向
      if (*(end - 1) != '>')
      {//ls > log.txt
        redir = OUTPUT_REDIR;
        (*(end - 1)) = '\0';
        end++;//指向要被重定向的文件的首字母
        end++;
        break;
      }
      else 
      {//ls >> log.txt
        //追加重定向
        redir = APPEND_REDIR;
        (*(end - 2)) = '\0';
        end++;//指向要被重定向的文件的首字母
        end++;
        break;
      }
    }
    else
    {
      end--;
    }
  }

  if (end >= start)
  {
    return end;
  }
  return NULL;
}




////ls > log.txt
//char* CheckReDir(char* start)
//{
//  char* end = start + strlen(start) - 1;
//  
//  //printf("%d\n", strlen(start));
//  
//  while (end >= start)//从后向前找是否有重定向符号
//  {
//    if ((*end) == '<')
//    {//ls < log.txt
//      //输出重定向
//      redir = OUTPUT_REDIR;
//      //(*(end-1)) = '\0';
//      (*(end)) = '\0';
//      end++;//指向要被重定向的文件的首字母
////      end++;
//      break;
//    }
//    else if ((*end) == '>')
//    {
//      //输入重定向
//      if (*(end - 1) != '>')
//      {//ls > log.txt
//        redir = INPUT_REDIR;
//        (*(end - 1)) = '\0';
//        end++;//指向要被重定向的文件的首字母
////        end++;
//        break;
//      }
//      else 
//      {//ls >> log.txt
//        //追加重定向
//        redir = APPEND_REDIR;
//        (*(end - 2)) = '\0';
//        end++;//指向要被重定向的文件的首字母
////        end++;
//        break;
//      }
//    }
//    else
//    {
//      end--;
//    }
//  }
//
//  //if (end >= start)
//  {
//    return end;
//  }
//  return NULL;
//}


int main()
{
  //0.始终不停止
  while (1)
  {
    //1.打印类似于[xkjtx@locathost shell@]的信息
    printf("[xkjtx@locatehost myshell]# ");//不要换行，否则就不像了，用fflush
    fflush(stdout);

    //2.获取命令行
    char cmd_line[NUM];//可以定义为全局变量，注意每次使用前memset(cmd_line, '\0', sizeof cmd_line);
    if (fgets(cmd_line, SIZE, stdin) == NULL)//推荐使用fgets，建立对标准输入输出流概念，方便以后学习基础IO
    {
      continue;//读取失败，重新读取
    }

    cmd_line[strlen(cmd_line) - 1] = '\0';

   /*下面的代码用来检查   是否  有重定向   的命令*/
    char* sep = CheckReDir(cmd_line);



    //char* fgets(char* str, int size, FILE* stream);//如果输入成功，则返回str的起始地址，否则为NULL
    //3.拆分命令行
    char* g_argv[NUM];
    g_argv[0] = strtok(cmd_line, SEP);//第一次调用第一个参数传参目标字符串，后面都传NULL
    int index = 1;


    //使得ls命令时带上颜色
    if (strcmp(g_argv[0], "ls") == 0)
    {
      g_argv[index++] = "--color=auto";
    }
   
    //使得ll命令也可以执行
    if (strcmp(g_argv[0], "ll") == 0)
    {
      g_argv[0] = "ls";
      g_argv[index++] = "-l";
      g_argv[index++] = "--color=auto";
    }
    
    while(g_argv[index++] = strtok(NULL, SEP));

    

    if (strcmp(g_argv[0], "export") == 0)//export:设置环境变量，注意要放在while分割字符串的后面，因为要用到g_argv[1]
    {
      char envv[SIZE];
      strcpy(envv, g_argv[1]);
      putenv(envv);
      //putenv(char* str);设置环境变量，要用函数来设置环境变量的原因：系统不允许我们直接修改环境变量，因此要借助函数
      
      continue;//后面的就不执行了
    }

  //  //检测分割是否正确
  // for (index = 0; g_argv[index]; index++)//C99标准不支持在for循环内定义int
  //   printf("g_argv[%d] = %s\n", index, g_argv[index]);
  
    //4.TODO
    //内置命令的处理（要父进程去执行的命令，如cd命令）
    //目前写法发现：cd ~, cd -, rm, export, vim等命令做不了
    


    //错误写法
    // if (g_argv[0] == "cd")
    if (strcmp(g_argv[0], "cd") == 0)
    {
      if (g_argv[1] != NULL)//路径合法
      {
        chdir(g_argv[1]);
        continue;//不去创建子进程
      }
    }



    //5.创建子进程//好处：不会影响父进程//shell本来就是媒婆招实习生，让实习生干活，不影响媒婆
    pid_t id = fork();
   

    int status = 0;

    if (id == 0)//child
    {
      /*下面的代码为重定向的操作,注意一定要在id = 0的时候才可以操作*/
      if (sep != NULL)
      {
        //以下部分可以用switch来做，注意switch中不可以定义变量
        int fd;
        if (redir == OUTPUT_REDIR)
        {//输出重定向：将本来输出到屏幕的数据输出到sep所指文件
          // int open(const char *pathname, int flags, mode_t mode);
          fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
          dup2(fd, 1);
        }
        else if (redir == INPUT_REDIR)
        {//输入重定向：将本来从键盘输入改为从sep所指文件输入       
          fd = open(sep, O_RDONLY);
          dup2(fd, 0);
        }
        else if (redir == APPEND_REDIR)
        {//追加重定向：将数据追加到sep所指文件       
          fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
          dup2(fd, 1);
        }
        else 
        {
          printf("bug?\n");
        }
      }



      execvp(g_argv[0], g_argv);//选这个函数的原因：(1)v：用的是数组，恰好有数组  (2)每个都要绝对路径，这是没有必要的，因此可以使用带p的函数
      exit(1);//进行到这里，一定是函数调用失败
    }

    //这里一定是父进程
    //father
    //获取子进程信息
    pid_t ret = waitpid(-1, &status, 0);
    /*
       #include <sys/types.h>
       #include <sys/wait.h>

       pid_t wait(int *status);

       pid_t waitpid(pid_t pid, int *status, int options);

     */
    if (ret > 0)
    {
      printf("exit code = %d\n", WEXITSTATUS(status));
    }
  }
  return 0;
}

